package net.cyndeline.entitySystem.defaultImpl

import scala.collection.immutable.Stack
import net.cyndeline.entitySystem.IdManager
import language.postfixOps

/**
 * Keeps track of what the next available entity id should be.
 * The current highest assigned id is stored, as well as a collection of
 * available id's below that value (filled as old entities are retired).
 */
class DefaultIdManager extends IdManager {
  val default = 0
  /* 0 is a null id, but won't be passed since the first 
   * getNext will give 0 + 1.
   */
  private var highestAssignedId = default
  private var freeIds = List[Int]()
  
  /*
   * If the user specifies what the next value returned should be, it
   * is assigned here. Since specifying a value above the current highest
   * will lead to additional free ids being stored, the value wouldn't
   * be returned until those have been assigned. However, this one value
   * will be returned if it exists before falling back to emptying the free
   * ids first.
   */
  private var nextSpecified: Option[Int] = None
  
  /**
   * Calling this method will modify the manager state, do not use it
   * unless the id is to be assigned to an integer. To check the next
   * id, use viewNext instead.
   * @return The next available entity id.
   */
  override def getNext = if (freeIds isEmpty) {
    
    if (highestAssignedId == Int.MaxValue) {
      throw new Error("No more avilable entity ids, current amount is " + highestAssignedId)
    } else {
      highestAssignedId = highestAssignedId + 1
      highestAssignedId
    }
    
  } else if (nextSpecified isDefined) {
      highestAssignedId = (nextSpecified get)
      nextSpecified = None
      highestAssignedId
      
  } else {
    val id = (freeIds(0))
    freeIds = (freeIds drop 1)
    id
  }
  
  override def viewNext: Int = {
    if (nextSpecified isDefined)
      return (nextSpecified get)
      
    if (freeIds isEmpty) 
      highestAssignedId + 1 
    else (freeIds(0))
  }

  override def setNext(id: Int) {
    if (id <= highestAssignedId) {
      throw new IllegalArgumentException("Attempted to set " + id + " as next " +
      		"entity id, but the manager has already assigned ids up to " +
      		"the value " + highestAssignedId)
      
    } else if (id == highestAssignedId + 1){
      // Do nothing. id will be returned next time anyway.

    } else {
      val oldHighest = highestAssignedId
      highestAssignedId = 0
      nextSpecified = Option(id)
      for (free <- oldHighest + 1 until id) {
        freeId(free)
      }
      
      
    }
  }
  
  override def isAvailable(id: Int): Boolean = freeIds contains id
  
  override def freeId(id: Int) {
    if (freeIds contains id) return
    
    if (id > Math.max(highestAssignedId, nextSpecified getOrElse 0)) {
      throw new IllegalArgumentException(
          "Attempted to free an entity id (" + id + "), but the " +
          		"highest assigned id is " + highestAssignedId + ".")
    } else if (id == highestAssignedId) {
      highestAssignedId = highestAssignedId - 1
      
    } else {
      freeIds = freeIds :+ id
    }
    
    freeIds = freeIds.sortWith(_ > _)
  }
  
  override def getFreeIds: List[Int] = freeIds
  
  override def setFreeIds(ids: List[Int]) {
    for (id <- ids) {
      freeId(id)
    }
  }
  
  override def clear {
    highestAssignedId = default
    freeIds = List[Int]()
    nextSpecified = None
  }
}